/*
=======================================================================

  Berzerker - BOTS
  
=======================================================================
*/
#include "g_local.h"

#include "bots_game2cgame.h"

#include "bg_local.h"
#include "bots_local.h"
#include "bots_berzerker.h"

void bots_updateClientData_berzerker(gentity_t *self, bots_g2cg_berzerker_t *p) {
	if (!self->client)
		return;

	BOTS_SETBIT(p->bits, self->client->qb_togquad,	BOTS_G2CG_BERZERKER_AUTOQUAD);
	p->sQuadTime	= self->client->qb_togquad?ceil((self->client->i_quadStartTime-level.time)/1000):0;

	p->sChargeTime	= ceil((self->client->i_nextChargeTime - level.time)/1000);
	p->sParasites	= self->client->iOnboard;
}
/////////////////////////////////////////////
//
// BOTS_calc_other_gaunt_vectors
//
// This function will calculate other guantlet vectors to do damage in other
// directions - beefing the gauntlet up a bit.
//
qboolean BOTS_calc_other_gaunt_vectors(gentity_t* ent, int damage, vec3_t forward, vec3_t right, vec3_t up, vec3_t muzzle)
{
	float		gauntletRange = 50.0f;
	vec3_t		max,min,start;
	int			i, e, numListedEntities, entityList[MAX_GENTITIES], hits = 0;


	if (!ent->client)
		return qfalse;

	// give him some range
	// gauntletRange += 20.0f * ((float) (ent->client->pers.classlevel+1) / 2.0f);

	gauntletRange /= 2.0f;
	VectorMA(muzzle, gauntletRange, forward, start);
	VectorCopy(start,min);
	VectorCopy(start,max);
	for (i=0; i<3; i++) {
		max[i] += gauntletRange;
		min[i] -= gauntletRange;
	}
	
	numListedEntities = trap_EntitiesInBox( min, max, entityList, MAX_GENTITIES );
	for ( e = 0 ; e < numListedEntities ; e++ ) {
		gentity_t	*hit = &g_entities[entityList[ e ]];
		trace_t		tr;


		if (hit == ent)
			continue;
		if (!hit->takedamage)
			continue;
		
		trap_Trace (&tr, muzzle, NULL, NULL, hit->s.pos.trBase, ent->s.number, MASK_SHOT);
		if (tr.surfaceFlags & SURF_NOIMPACT)
			continue;
		
		if (hit->client) {
			gentity_t	*tent;


			if (tr.contents & CONTENTS_SOLID) { //  || !tr.entityNum ) {
				continue;
			}

			if (hit->client->trooptransport == ent)
				// Don't hit ppl we're transporting
				continue;
						
			tent = G_TempEntity( tr.endpos, EV_MISSILE_HIT );
			tent->s.otherEntityNum = hit->s.number;
			tent->s.eventParm = DirToByte( tr.plane.normal );
			tent->s.weapon = ent->s.weapon;
		}
		
		G_Damage( hit, ent, ent, forward, tr.endpos, damage, 0, MOD_GAUNTLET );
		hits++;
	}

	if (hits > 0) {
		return qtrue;
	}
	else
		return qfalse;
}

void BOTS_charge(int clientNum)
{
	gentity_t* ent = g_entities + clientNum;
	int modifier;

	// check to see if this class can use charge
	if (ent->bots_class != BOTS_BERZERKER)
	{
		BOTS_cp_console(clientNum, "Only the Berzerker can charge.");
		return;
	}

	if (ent->client->ps.powerups[PW_BLUEFLAG]	||
		ent->client->ps.powerups[PW_REDFLAG]	||
		ent->client->keyindex)
	{
		BOTS_cp_console(clientNum, "You can not charge with the flag.");
		return;
	}

	if (ent->client->i_nextChargeTime > level.time)
	{
		/*
		BOTS_cp_console(clientNum, va("You must wait %i seconds to charge.",
									   (ent->client->i_nextChargeTime - level.time)/1000));
		*/
		return;
	}

	modifier = ent->client->pers.classlevel;
	
	// I want the zerk to have to wait a lot longer at higher levels for the charge
	// tbp: modifed a bit 60s*4=360s was a tad too much ;)
	ent->client->i_nextChargeTime = level.time + ((float)CHARGE_DELAY * (1.0f + ((float)modifier/4.0f)));

	//ent->client->qb_chargeMode = qtrue;
	if (ent->client->pers.classlevel == 0)
		modifier = 1;
	ent->client->ps.powerups[PW_HASTE] = level.time + (CHARGE_TIME * modifier);
}

void BOTS_rage(int clientNum)
{
	gentity_t* ent = g_entities + clientNum;
	float modifier = 1.0;

	// check to see if this class can use rage
	if (ent->bots_class != BOTS_BERZERKER)
	{
		BOTS_cp_console(clientNum, "Only the Berzerker can rage.");
		return;
	}

	if (ent->client->ps.powerups[PW_BLUEFLAG]	||
		ent->client->ps.powerups[PW_REDFLAG]	||
		ent->client->keyindex)
	{
		BOTS_cp_console(clientNum, "You can not rage with the flag.");
		return;
	}

	if (ent->client->qb_rageMode)
		return;

	if (ent->client->sess.sessionTeam == TEAM_RED)
		modifier = .75;

	if (ent->health > (99 * modifier)) 
	{
		BOTS_cp_console(clientNum, "You have too much health to rage.");
		return;
	}

	ent->client->qb_rageMode = qtrue;
	ent->client->i_rageStopTime = level.time + (int)(200 * ent->health);
	ent->client->ps.powerups[PW_FLIGHT] = ent->client->i_rageStopTime;
}

void BOTS_togquad(int clientNum)
{
	gentity_t* ent = g_entities + clientNum;

	// check to see if this class can use rage
	if (ent->bots_class != BOTS_BERZERKER)
	{
		BOTS_cp_console(clientNum, "Only the Berzerker toggle quad.");
		return;
	}

	if (ent->client->pers.classlevel < 2)
	{
		BOTS_cp_console(clientNum, "You must be at least level 2 to toggle quad.");
		return;
	}

	ent->client->qb_togquad = !ent->client->qb_togquad;

	ent->client->ps.powerups[PW_QUAD] = 0;

	if (ent->client->qb_togquad) {
		BOTS_cp_console(clientNum, "Quad is enabled.");
		if (ent->bots_team == TEAM_RED)
			ent->client->i_quadStartTime = level.time + ZERK_QUAD_RED;
		else
			ent->client->i_quadStartTime = level.time + ZERK_QUAD_BLUE;
	}
	else
		BOTS_cp_console(clientNum, "Quad is disabled.");
}

qboolean BOTS_BerzerkerCommands(int clientNum, char* cmd)
{
	if (Q_stricmp (cmd, "charge") == 0 )
	{
		BOTS_charge(clientNum);
		return qtrue;
	}
	else if (Q_stricmp (cmd, "rage") == 0 )
	{
		BOTS_rage(clientNum);
		return qtrue;
	}
	else if (Q_stricmp (cmd, "togquad") == 0)
	{
		BOTS_togquad(clientNum);
		return qtrue;
	}
	else if (Q_stricmp (cmd, "repel") == 0) {
		gentity_t *zerk = g_entities + clientNum;

		if (zerk->client->iOnboard > 0)
			BOTS_BerzerkerReleaseTroops(zerk);
		else 
			BOTS_cp_console(clientNum, "No parasite to repel.");
		return qtrue;
	}
			
	return qfalse;
}

/////////////////////////////////////////////
//
// BOTS_ClientThink_berzerker
//
// Berzerker think routine.  Called once for each client think if
// the client is a berzerker.
//
void BOTS_ClientThink_berzerker(gentity_t* ent)
{
	if (ent->health <= 0)
		return;

	if (ent->client->qb_togquad && level.time > ent->client->i_quadStartTime)
	{
		// Cumulate quad effects
		if (ent->client->ps.powerups[PW_QUAD] != 0)
			ent->client->ps.powerups[PW_QUAD] += 10000;
		else
			ent->client->ps.powerups[PW_QUAD] = level.time + 10000;


		if (ent->bots_team == TEAM_RED)
			ent->client->i_quadStartTime = level.time + ZERK_QUAD_RED;
		else
			ent->client->i_quadStartTime = level.time + ZERK_QUAD_BLUE;

		ent->client->ps.stats[STAT_ARMOR] = 0;
	}

	if (ent->client->i_rageStopTime > level.time)
		ent->takedamage = qfalse;
	else
	{
		// rage has expired... kill the zerk
		if (ent->client->qb_rageMode)
		{
			ent->takedamage = qtrue;
			ent->client->qb_rageMode = qfalse;
			ent->flags &= ~FL_GODMODE;
			ent->client->ps.stats[STAT_HEALTH] = 0;
			ent->health = 0;
			player_die (ent, ent, ent, 100000, MOD_SUICIDE);
		}
	}
}

// BEGIN bots - tbp
// -----------------------------------------------------------------------------------------------------
// Some support routines for zerk transporting troops
qboolean BOTS_isTransporting(int selfNum) {
	gentity_t *self	= &g_entities[selfNum];

	return ((self->bots_class == BOTS_BERZERKER) && (self->client->iOnboard > 0))?qtrue:qfalse;
}

qboolean BOTS_isTransported(int selfNum) {
	return g_entities[selfNum].client->trooptransport?qtrue:qfalse;
}

// Re-compute ranks for ppl being carried
void BOTS_BerzerkerComputeTroopRanks(gentity_t *zerk) {
	gentity_t 
		*p = g_entities;
	int i,rank = 0;

	if (zerk->client->iOnboard == 0)
		return;

	for (i=0; i<MAX_GENTITIES; i++) { 
		if (p->classname && (!strcmp(p->classname, "hook")) && (p->enemy == zerk)) {
			p->parent->client->iTroopRank = rank++;
		}
		p++;
	}
	if (rank != zerk->client->iOnboard)
		G_Printf(va("BOTS_BerzerkerComputeTroopRanks: OOPS, %i != %i\n",rank,zerk->client->iOnboard));

}

float BOTS_BerzerkerComputeAngleFromRank(gentity_t *zerk, int rank) {
	const float fANGULAR_SPREAD = 90.0f;

	if (zerk->client->iOnboard > 1) {
		// Try to not stick all of them at the same position
		if (rank > zerk->client->iOnboard)
			rank = zerk->client->iOnboard;
		return 2.0f * M_PI * ((zerk->client->ps.viewangles[YAW] + 180.0f + ((float)rank*(fANGULAR_SPREAD/(float)(zerk->client->iOnboard - 1))) - (fANGULAR_SPREAD/2.0f))/360.0f);
	}
	else
		return  2.0f * M_PI * ((zerk->client->ps.viewangles[YAW] + 180.0f)/360.0f);
}

void BOTS_nudgePlayer(gentity_t *ent,gentity_t *zerk) {
	// see G_TouchTriggers() in g_active.c
	// see PM_SlideMove() bg_slidemove.c
	vec3_t			pos,end;
	trace_t			trace;
	int				bumpcount;


	if ( !ent->client || !zerk->client )
		return;

	VectorCopy( ent->client->ps.origin, pos );
	VectorCopy( ent->client->ps.origin, end );
	trap_Trace(&trace, pos, ent->r.mins, ent->r.maxs, end, ent - g_entities, MASK_PLAYERSOLID);
	if (!trace.allsolid && !trace.startsolid) {
		// G_Printf("BOTS_nudgePlayer: no need :)\n");
	}
	else {
		float			yaw;
		const float		fDISTANCE = 40.0f/2.0f;
		
		G_Printf(va("BOTS_nudgePlayer: allsolid %i startsolid %i\n",trace.allsolid,trace.startsolid ));
		// Try to find some free space, somewhere
		yaw = BOTS_BerzerkerComputeAngleFromRank(zerk, ent->client->iTroopRank);
		for ( bumpcount=0 ; bumpcount < 4 ; bumpcount++ ) {
			VectorCopy( zerk->client->ps.origin, pos );
			pos[0] += cos(yaw) * fDISTANCE * (float) (bumpcount+1);
			pos[1] += sin(yaw) * fDISTANCE * (float) (bumpcount+1);
			VectorCopy( ent->client->ps.origin, end );
			pos[0] += cos(yaw) * fDISTANCE * (float) (bumpcount+3);
			pos[1] += sin(yaw) * fDISTANCE * (float) (bumpcount+3);
			
			trap_Trace(&trace, pos, ent->r.mins, ent->r.maxs, end, ent - g_entities, MASK_PLAYERSOLID);
			if (!trace.startsolid) {
				G_Printf(va("BOTS_nudgePlayer: %i pass\n",bumpcount+1));
				VectorCopy(pos,ent->client->ps.origin);
				return;
			}
			trap_Trace(&trace, end, ent->r.mins, ent->r.maxs, pos, ent - g_entities, MASK_PLAYERSOLID);
			if (trace.fraction > 0) {
				G_Printf(va("BOTS_nudgePlayer: %i pass (inv)\n",bumpcount+1));
				VectorCopy(trace.endpos,ent->client->ps.origin);
				return;
			}
		}
		G_LogPrintf("BOTS_nudgePlayer: OOPS!\n");
	}
}


// Called each time a player wants to get off a zerk (either by dying or something)
void BOTS_BerzerkerReleaseOne(gentity_t *trooper) {
	gentity_t *zerk = trooper->client->trooptransport;

	// It might be called when the player itself has already been released, ie from Weapon_HookFree()
	if (trooper->client->trooptransport) {
		G_Printf(va("BOTS_BerzerkerReleaseOne: releasing %s (from %s)\n",trooper->client->pers.netname, zerk->client->pers.netname));
		trooper->client->trooptransport = NULL;
		if (zerk->client->iOnboard > 0)
			zerk->client->iOnboard--;
		else
			G_Printf(va("BOTS_BerzerkerReleaseOne: OOPS, already empty (%i)\n",zerk->client->iOnboard));
		
		// Now we should nudge that player's position to avoid letting him get stuck in the zerker once he's released.
		// (do something fancy with the forward vector for the zerk)
		BOTS_nudgePlayer(trooper,zerk);

		// Tell him what's happening.
		BOTS_cp_console(trooper - g_entities, "You've been released from a zerk.");
	}
}

// Called when the zerk dies (ie),
// Tracks all player being carried and any hook entities hanging around
void BOTS_BerzerkerReleaseTroops(gentity_t *zerk) {
	gentity_t 
		*p = g_entities;
	int i,count = zerk->client->iOnboard;

	// Should also track hook ents and set their nextthink to free
	// G_Printf(va("BOTS_BerzerkerReleaseTroops: releasing for zerk %s\n",zerk->client->pers.netname));
	for (i=0; i<MAX_GENTITIES; i++) { 
		if ((p->client) && (p->client->trooptransport == zerk)) {
			// Found one trooper
			BOTS_BerzerkerReleaseOne(p);
			count--;
		}
		if (p->classname && (!strcmp(p->classname, "hook")) && (p->enemy == zerk)) {
			// Found a trooper's hook ent
			// G_Printf("BOTS_BerzerkerReleaseTroops: 1 trooper's hook released\n");
			p->think = Weapon_HookFree;
			p->nextthink = level.time + 10;
		}
		p++;
	}
	if (count != 0)
		G_LogPrintf(va("BOTS_BerzerkerReleaseTroops: OOPS, count = %i\n",count));
	zerk->client->iOnboard = 0;
}

// Some routines for fixing collisions between zerk and ppl they carry.
gentity_t *troopClients[MAX_CLIENTS];
void BOTS_kludgeUnLink(int zerkNum, int excludeNum) {
	int i,count = 0;
	gentity_t 
		*zerk = &g_entities[zerkNum],
		*exclude = &g_entities[excludeNum],
		*p = g_entities;


	memset(troopClients,0,sizeof(troopClients));
	for (i=0; i<MAX_GENTITIES; i++) { 
		if ( (p != exclude) && (p->client) && (p->client->trooptransport == zerk)) {
			troopClients[count++] = p;
			trap_UnlinkEntity(p);
		}
		p++;
	}
	// If it's for a troop, should also remove the zerk
	if (zerkNum != excludeNum) {
		troopClients[count++] = zerk;
		trap_UnlinkEntity(zerk);
	}
	// G_Printf(va("UnLink: zerk %i exclude %i ents %i / ",zerkNum,excludeNum,count));
}

void BOTS_kludgeReLink() {
	int i = 0;
	gentity_t **p = troopClients;

	while ( (i<MAX_CLIENTS) && (*p) ) {
		trap_LinkEntity(*(p++));
		i++;
	}
	// G_Printf(va("ReLink: ents %i\n",i));
}

void BOTS_kludgeUnLinkForZerk(int zerkNum) {
	BOTS_kludgeUnLink(zerkNum,zerkNum);
}

void BOTS_kludgeUnLinkForTroop(int troopNum) {
	BOTS_kludgeUnLink(g_entities[troopNum].client->trooptransport - g_entities,troopNum);
}
// END bots - tbp
